using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;


enum UIState
{
    Show,
    Hide,
    Release,
    Action
}
struct Operator
{
    public UIState uiState;
    public UI ui;
    public Action action;

    /// <summary>
    /// 判断是否相同的操作。
    /// 注意：无法判断是否为同一个回调，所以这里忽略了回调的判断
    /// </summary>
    /// <param name="lhs"></param>
    /// <param name="rhs"></param>
    /// <returns></returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(Operator lhs, Operator rhs)
    {
        return lhs.uiState == rhs.uiState && lhs.ui == rhs.ui;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(Operator lhs, Operator rhs)
    {
        return !(lhs == rhs);
    }
}

public partial class UIManager : Singleton<UIManager>
{
    [SerializeField]
    private RectTransform uiLayer;
    public GameObject loadingUI;
    public GameObject bgUI;
    Dictionary<UI, GameObject> cachePrefab = new Dictionary<UI, GameObject>();
    Dictionary<UI, GameObject> cacheUI = new Dictionary<UI, GameObject>();
    List<Operator> operators = new List<Operator>();
    private List<List<Operator>> unfinishedOperators = new();
    private Queue<List<Operator>> pool = new();
    
    public UIManager Show(UI ui)
    {
        operators.Add(new Operator
        {
            uiState = UIState.Show,
            ui = ui
        });
        return this;
    }

    public UIManager Hide(UI ui)
    {
        operators.Add(new Operator
        {
            uiState = UIState.Hide,
            ui = ui
        });

        return this;
    }

    public UIManager Destroy(UI ui)
    {
        operators.Add(new Operator
        {
            uiState = UIState.Release,
            ui = ui
        });
        return this;
    }

    public UIManager Run(Action action)
    {
        operators.Add(new Operator
        {
            uiState = UIState.Action,
            action = action
        });
        return this;
    }

    public async void Done()
    {
        if (operators.Count <= 0)
        {
            return;
        }
        // 判断当前ui操作是不是上一个未完成的
        var hasSame = false;
        foreach (var items in unfinishedOperators)
        {
            if (items.Count == operators.Count)
            {
                var isSame = true;
                for (int i = 0; i < items.Count; i++)
                {
                    if (items[i] != operators[i])
                    {
                        isSame = false;
                        break;
                    }
                }

                if (isSame)
                {
                    hasSame = true;
                    break;
                }
            }
        }
        if (hasSame)
        {
            operators.Clear();
            return;
        }

        var tmpOperators = pool.Count > 0 ? pool.Dequeue() : new List<Operator>();
        tmpOperators.AddRange(operators);
        unfinishedOperators.Add(tmpOperators);
        operators.Clear();
        // 创建一个新的列表缓存操作
        // 在await ShowUI时，可能这个过程太长，在这中间有其他显示ui的操作。
        // 当执行Done的时候，operators发生改变，而上个协程还没有执行玩，会
        // 导致生成2个ui的情况

        //loadingUI.SetActive(loadingUIEnable);

        for (int i = 0; i < tmpOperators.Count; i++)
        {
            if (tmpOperators[i].uiState == UIState.Show)
            {
                await ShowUI(tmpOperators[i].ui);
            }
            else if (tmpOperators[i].uiState == UIState.Release)
            {
                DestroyUI(tmpOperators[i].ui);
            }
            else if (tmpOperators[i].uiState == UIState.Action)
            {
                tmpOperators[i].action.Invoke();
            }
            else
            {
                HideUI(tmpOperators[i].ui);
            }
        }
        //loadingUI.SetActive(false);
        unfinishedOperators.Remove(tmpOperators);
        tmpOperators.Clear();
        pool.Enqueue(tmpOperators);
    }

    public async UniTask<UIManager> ShowUI(UI ui)
    {

        if (cacheUI.TryGetValue(ui, out var obj))
        {
            obj.transform.parent.localPosition = new Vector3(0, 0, -200);
            SetUIState(obj, true);
            return this;
        }

        try
        {
            var uiName = ui.ToString();
            var prefab = await AssetLoader.Load<GameObject>(uiName, uiName);
            var uiObj = Instantiate(prefab, uiLayer);
            uiObj.name = uiName;
            uiObj.AddComponent<CanvasGroup>();
            cachePrefab[ui] = prefab;
            cacheUI[ui] = uiObj;
            uiObj.GetComponent<RectTransform>().localPosition = new Vector3(0, 0, -200);
            SetUIState(uiObj, true);
            Debug.Log($"UIManager::ShowUI {ui}");
        }
        catch (Exception e)
        {
            Debug.LogError($"error[{ui}]: {e}");
            throw;
        }

        return this;
    }

    public void HideUI(UI ui)
    {
        if (cacheUI.TryGetValue(ui, out var uiObj))
        {
            SetUIState(uiObj, false);
        }
    }

    public void DestroyUI(UI ui)
    {
        if (cacheUI.TryGetValue(ui, out var uiObj))
        {
            SetUIState(uiObj, false);
            EventManager.DeregisterAll(uiObj.GetComponent<IUI>());
            DestroyImmediate(uiObj);
            AssetLoader.Release(ui.ToString());
            cachePrefab.Remove(ui);
            cacheUI.Remove(ui);
        }
    }

    void SetUIState(GameObject go, bool visible)
    {
        var canvasGroup = go.GetComponent<CanvasGroup>();
        if (visible)
        {
            go.GetComponent<IUI>().OnShow();
            canvasGroup.alpha = 1.0f;
            canvasGroup.blocksRaycasts = true;
        }
        else
        {
            go.GetComponent<IUI>().OnHide();
            canvasGroup.alpha = 0.0f;
            canvasGroup.blocksRaycasts = false;
        }
    }

    public GameObject GetUI(UI ui)
    {
        return cacheUI.GetValueOrDefault(ui);
    }

    public T GetUI<T>(UI ui) where T : IUI
    {
        return cacheUI[ui].GetComponent<T>();
    }
}
